﻿using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Serialization;

namespace ProjectTW.Foliage
{
    public delegate void SectorRecalculated(List<ChunkBase> newChunks, Vector2 newChunkSize);

    /// <summary>
    /// A sector which is used to divide the UNTerrain objects in the world to increase performance (can handle more than 200k trees!!)
    /// </summary>
    [System.Serializable]
    public class SectorBase : MonoBehaviour
    {
        public const int Resolution_Limit = 40;

        public int SectorResolution;
        public List<ChunkBase> Chunks = new List<ChunkBase>();

        [SerializeField]
        private Vector2 m_ChunkSize = Vector2.zero;

        [System.NonSerialized]
        private List<ChunkBase> s_TemportaryChunkThreshold;

        public Vector2 ChunkSize
        {
            get
            {
                return m_ChunkSize;
            }
        }

        private List<ChunkBase> TemportaryChunkThreshold
        {
            get
            {
                if (s_TemportaryChunkThreshold == null)
                {
                    s_TemportaryChunkThreshold = new List<ChunkBase>(Chunks.Count / 10);
                }

                return s_TemportaryChunkThreshold;
            }
        }

        public virtual void OnCreated(Transform owner, int resolution)
        {
            this.SectorResolution = resolution;
        }

        public virtual void Awake()
        {
        }

        public void ResetChunks()
        {
            for (int i = 0; i < Chunks.Count; i++)
            {
                Chunks[i].ResetChunk();
            }
        }

        public static T GenerateSector<T, T1>(Transform owner, Vector3 bounds, T sector, int resolution) where T : SectorBase where T1 : ChunkBase
        {
            resolution = Mathf.Clamp(resolution, 5, Resolution_Limit);

            if (sector == null)
            {
                GameObject obj = new GameObject("Sector");
                obj.transform.parent = owner;
                obj.transform.localPosition = Vector3.zero;

                sector = obj.AddComponent<T>();
            }

            sector.SectorResolution = resolution;

            Vector2 size = new Vector2(bounds.x / resolution, bounds.z / resolution);
            Vector2 position = new Vector2();

            sector.OnStartCreatingChunks();

            sector.m_ChunkSize = size;

            T1 chunkInstance;
            short chunkCount = 0;

            for (int y = 0; y < sector.SectorResolution; y++)
            {
                for (int x = 0; x < sector.SectorResolution; x++)
                {
                    position.x = x * size.x;
                    position.y = y * size.y;

                    chunkInstance = ChunkBase.CreateChunk<T1>(sector, position, x, y, size, chunkCount);

                    sector.Chunks.Add(chunkInstance);
                    sector.OnChunkCreated(chunkInstance);

                    chunkCount++;
                }
            }

            sector.OnCreated(owner, resolution);

            sector.OnResolutionChanged();

            return sector;
        }

        protected virtual void OnResolutionChanged()
        {
        }

        protected virtual void OnChunkCreated(ChunkBase chunkBase)
        {
        }

        protected virtual void OnStartCreatingChunks()
        {
            for (int i = 0; i < Chunks.Count; i++)
            {
                if (Chunks[i] == null)
                {
                    continue;
                }

                GameObject.DestroyImmediate(Chunks[i].gameObject);
            }

            Chunks.Clear();
        }

        public ChunkBase GetChunk(Vector2 pos, float offset)
        {
            return GetChunk(new Vector3(pos.x, 0, pos.y), offset);
        }

        public ChunkBase GetChunk(Vector3 pos, float offset)
        {
            List<ChunkBase> chunks = GetChunks(pos, offset, true);

            return chunks.Count == 0 ? null : chunks[0];
        }

        public ChunkBase GetChunk(Vector3 pos)
        {
            return Chunks[Mathf.FloorToInt(pos.x / m_ChunkSize.x) + Mathf.FloorToInt(pos.z / m_ChunkSize.y) * SectorResolution];
        }

        public List<ChunkBase> GetChunks(Vector2 pos, float offset, bool sortResult)
        {
            return GetChunks(new Vector3(pos.x, 0, pos.y), offset, sortResult);
        }

        public List<ChunkBase> GetChunks(Vector3 pos, float offset, bool sortResult)
        {
            TemportaryChunkThreshold.Clear();

            ChunkBase chunkBase;

            for (int i = 0; i < Chunks.Count; i++)
            {
                chunkBase = Chunks[i];

                if (chunkBase != null)
                {
                    if (chunkBase.Contains(pos, offset))
                    {
                        TemportaryChunkThreshold.Add(chunkBase);
                    }
                }
            }

            if (sortResult)
            {
                TemportaryChunkThreshold.Sort(delegate(ChunkBase a, ChunkBase b)
                {
                    if (a == null || b == null)
                    {
                        return 0;
                    }

                    return Vector2.Distance(a.Position, pos).CompareTo(Vector2.Distance(b.Position, pos));
                });
            }

            return TemportaryChunkThreshold;
        }

        public virtual void ApplicationQuit()
        {
        }
    }
}
